﻿/*
 *  功能：
 *  1、监听蓝牙，接受已经配对的客户端的连接请求
 *  2、根据蓝牙物理地址，主动spp连接蓝牙
 *  3、简历蓝牙socket连接后，接受和发送信息
 *  
 */

package com.robotcontroller.buaa;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/*
 * 主要成员：
 * 线程mAcceptThread，监听本地蓝牙，接受客户端的蓝牙socket连接请求，此时本地蓝牙是服务器端
 * 线程mConnectThread，根据远程蓝牙的物理地址，主动请求socket连接远程蓝牙，此时本地蓝牙是客户端
 * 线程mConnectedTrhead，与远程蓝牙双工通信（已经建立好了蓝牙socket连接），接受和发送信息
 */
public class BluetoothService {
    // Debugging
    private static final String TAG = "BluetoothService";
    private static final boolean D = true;

    // Name for the SDP record when creating server socket
    private static final String NAME = "MainActivity";

    //SDP服务的UUID是00001101-0000-1000-8000-00805F9B34FB
    //蓝牙虚拟串口，SPP服务的UUID，"00001101-0000-1000-8000-00805F9B34FB"
    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    
    
    // Member fields
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private AcceptThread mAcceptThread;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;

    //蓝牙socket几种状态
    public static final int STATE_NONE = 0;       // 其他状态
    public static final int STATE_LISTEN = 1;     // 正在监听
    public static final int STATE_CONNECTING = 2; // 正在建立socket连接
    public static final int STATE_CONNECTED = 3;  // 已经建立了socket连接

    /**
     * 构造函数
     * @param context  The UI Activity Context
     * @param handler  用于给MainActivity发送消息的Handler
     */
    public BluetoothService(Context context, Handler handler) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
        mHandler = handler;
    }

    /**
     * 设置当前的蓝牙socket网络状态
     * @param state 当前的网络状态
     */
    private synchronized void setState(int state) {
        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
        mState = state;

        //Handler发送消息，通知当前网络状态发生改变
        mHandler.obtainMessage(MainActivity.BTMSG_STATE_CHANGE, state, -1).sendToTarget();
    }

    /**
     * 返回当前网络状态 */
    public synchronized int getState() {
        return mState;
    }

    /**
     *开启BluetoothService监听，设置为监听模式，监听本地蓝牙，等待客户端的socket连接请求
     */
    public synchronized void start() {
        if (D) Log.d(TAG, "start");

        //关闭正在连接的socket
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

        //关闭已经连接的socket
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        //开始socket监听
        if (mAcceptThread == null) {
            mAcceptThread = new AcceptThread();
            mAcceptThread.start();
        }
        setState(STATE_LISTEN);
    }

    /**
     * 开始socket连接远程蓝牙设备
     * @param device ，远程蓝牙设备
     */
    public synchronized void connect(BluetoothDevice device) {
        if (D) Log.d(TAG, "connect to: " + device);

        //关闭正在连接的socket
        if (mState == STATE_CONNECTING) {
            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        }

        //关闭已经连接的socket
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        //创建新线程去主动连接远程蓝牙设备
        mConnectThread = new ConnectThread(device);
        mConnectThread.start();
        setState(STATE_CONNECTING);
    }

    /**
     * 开启线程ConnectedThread去管理已经建立连接的socket
     * @param socket  The BluetoothSocket on which the connection was made
     * @param device  The BluetoothDevice that has been connected
     */
    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
        if (D) Log.d(TAG, "connected");

        //关闭正在试图连接的蓝牙socket
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

        //关闭已经建立连接的其他的蓝牙socket
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        //取消监听模式
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}

        //创建线程ConnectedThread去管理已经建立连接的socket，进行双工通信
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();

        //通知MainActivit网络状态改变
        Message msg = mHandler.obtainMessage(MainActivity.BTMSG_DEVICE_NAME);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.DEVICE_NAME, device.getName());
        msg.setData(bundle);
        mHandler.sendMessage(msg);

        setState(STATE_CONNECTED);
    }

    /**
     * 取消所有的线程
     */
    public synchronized void stop() {
        if (D) Log.d(TAG, "stop");
        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
        if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
        setState(STATE_NONE);
    }

    /**
     * 向已经建立连接的socket写数据
     * @param out The bytes to write
     * @see ConnectedThread#write(byte[])
     */
    public void write(byte[] out) {

        ConnectedThread r;
        
        //互斥操作       
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
     
        r.write(out);
    }

    /**
     * 向已经建立连接的socket写数据
     * @param out 欲发送字节所在数组
     * @param offset 偏移量
     * @param len 发送的字节数目
     * @see ConnectedThread#write(byte[] , int , int)
     */
    public void write(byte[] out , int offset , int len) {
       
        ConnectedThread r;
        //互斥操作
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
       
        r.write(out , offset , len);
    }
    
    /**
     *连接失败处理函数，设置为监听模式，通知MainAcitivity，连接失败
     */
    private void connectionFailed() {
        setState(STATE_LISTEN);

        //通知MainActvity，连接失败
        Message msg = mHandler.obtainMessage(MainActivity.BTMSG_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.TOAST, "Unable to connect device");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }

    /**
     * 已经建立的连接断开处理函数，设置为监听模式，通知MainActivity连接断开
     */
    private void connectionLost() {
        setState(STATE_LISTEN);

      //通知MainActvity，连接断开
        Message msg = mHandler.obtainMessage(MainActivity.BTMSG_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MainActivity.TOAST, "Device connection was lost");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }

    /**
     * 监听蓝牙socket的线程，此时本地蓝牙是蓝牙服务器端，处在监听模式
     * 
     */
    private class AcceptThread extends Thread {
        // The local server socket
        private final BluetoothServerSocket mmServerSocket;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;

            // Create a new listening server socket
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "listen() failed", e);
            }
            mmServerSocket = tmp;
        }

        public void run() {
            if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
            setName("AcceptThread");
            BluetoothSocket socket = null;

            // Listen to the server socket if we're not connected
            while (mState != STATE_CONNECTED) {
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    Log.e(TAG, "accept() failed", e);
                    break;
                }

                // If a connection was accepted
                if (socket != null) {
                    synchronized (BluetoothService.this) {
                        switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            // Situation normal. Start the connected thread.
                            connected(socket, socket.getRemoteDevice());
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            // Either not ready or already connected. Terminate new socket.
                            try {
                                socket.close();
                            } catch (IOException e) {
                                Log.e(TAG, "Could not close unwanted socket", e);
                            }
                            break;
                        }
                    }
                }
            }
            if (D) Log.i(TAG, "END mAcceptThread");
        }

        public void cancel() {
            if (D) Log.d(TAG, "cancel " + this);
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of server failed", e);
            }
        }
    }


    /**
     * 线程ConnectThread，负责主动连接远程蓝牙，阻塞式连接
     * 
     */
    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            setName("ConnectThread");

            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                // Start the service over to restart listening mode
                BluetoothService.this.start();
                return;
            }

            // Reset the ConnectThread because we're done
            synchronized (BluetoothService.this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

/*
 * 线程ConnectedThread，管理已经建立连接的socket连接，负责双工通信
 */
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
       
        private final DataInputStream mmInStream;
        private final DataOutputStream mmOutStream;
        

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            
            DataInputStream tmpIn = null;
            DataOutputStream tmpOut = null;
             

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn =  new DataInputStream(socket.getInputStream()); //socket.getInputStream();
                tmpOut = new DataOutputStream(socket.getOutputStream());
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] readbuffer = new byte[1024];
            int readbytes;
            int framelen;
            // Keep listening to the InputStream while connected
            while (true) {
                try {
                
                	 /*
                    // Read from the InputStream
                    readbytes = mmInStream.read(readbuffer);
                    
                    // Send the obtained bytes to the UI Activity
                    if(readbytes > 0)
                    {
                        mHandler.obtainMessage(MainActivity.MESSAGE_READ, readbytes, -1, readbuffer)
                        .sendToTarget();
                    }
                    */
                    

                    /*
                     * BB FF      04        01     00  00    FF
                     * 帧头              帧长度           设备ID     参数            帧尾
                     *1、所有的命令都是这种格式，
                     *2、先读取3个字节，第3个字节是帧长度，然后根据帧长度读取剩余的
                     * 
                     */
                      
                	mmInStream.readFully(readbuffer, 0, 3);
                	framelen = readbuffer[2];
                	mmInStream.readFully(readbuffer, 3 , framelen);
                	readbytes = 3 + framelen;
                	
                    mHandler.obtainMessage(MainActivity.BTMSG_READ, readbytes, -1, readbuffer)
                     .sendToTarget();
                            
                } catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
        }
        
    
        /*
         * 1、只有主动发送命令给机器人，机器人才发送信息给手机蓝牙端
         * 2、所以可以直接提取，没有必要另外去读输入流
         * 3、直接检查获取值就行、、
         */
        

        /**
         * Write to the connected OutStream.
         * @param buffer  The bytes to write
         */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

                // Share the sent message back to the UI Activity
                mHandler.obtainMessage(MainActivity.BTMSG_WRITE, -1, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }

        
        public void write(byte[] buffer , int offset , int len) {
            try {
                mmOutStream.write(buffer , offset , len);

                // Share the sent message back to the UI Activity
                mHandler.obtainMessage(MainActivity.BTMSG_WRITE, -1, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }
        
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
    
    
    //InputStream类型
    /*
    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
       
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
        

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
             

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] readbuffer = new byte[1024];
            int readbytes;
            int readnum;
            // Keep listening to the InputStream while connected
            while (true) {
                try {
                
                    // Read from the InputStream
                    readbytes = mmInStream.read(readbuffer);
                    

                    // Send the obtained bytes to the UI Activity
                    if(readbytes > 0)
                    {
                        mHandler.obtainMessage(MainActivity.MESSAGE_READ, readbytes, -1, readbuffer)
                        .sendToTarget();
                    }

                    
                      
                            
                } catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
        }
        
    
        
         // 1、只有主动发送命令给机器人，机器人才发送信息给手机蓝牙端
         // 2、所以可以直接提取，没有必要另外去读输入流
         // 3、直接检查获取值就行、、
         
        

        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

                // Share the sent message back to the UI Activity
                mHandler.obtainMessage(MainActivity.MESSAGE_WRITE, -1, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }

        
        public void write(byte[] buffer , int offset , int len) {
            try {
                mmOutStream.write(buffer , offset , len);

                // Share the sent message back to the UI Activity
                mHandler.obtainMessage(MainActivity.MESSAGE_WRITE, -1, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }
        
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
    */
    
}
